{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:38.236695Z",
     "start_time": "2025-02-26T12:19:35.074578Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 2.0.2\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.1\n",
      "torch 2.6.0+cu126\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.686894Z",
     "start_time": "2025-02-26T12:19:38.237689Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing()\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.691197Z",
     "start_time": "2025-02-26T12:19:41.687897Z"
    }
   },
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.731241Z",
     "start_time": "2025-02-26T12:19:41.691197Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.741973Z",
     "start_time": "2025-02-26T12:19:41.732244Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.750504Z",
     "start_time": "2025-02-26T12:19:41.742976Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.757162Z",
     "start_time": "2025-02-26T12:19:41.751505Z"
    }
   },
   "source": [
    "train_ds[1]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 7
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.760795Z",
     "start_time": "2025-02-26T12:19:41.757162Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 256\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 8
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "自定义 Layer"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.765376Z",
     "start_time": "2025-02-26T12:19:41.761788Z"
    }
   },
   "source": [
    "class CustomizedLinear(nn.Module):\n",
    "    def __init__(self, in_features, out_features):\n",
    "        super().__init__()\n",
    "        # 感兴趣的可以在这里实现其他的初始化方法\n",
    "        self.weights = nn.Parameter(torch.zeros(in_features, out_features)) # 定义权重\n",
    "        self.weights = nn.init.xavier_normal_(self.weights) # 使用xavier_normal初始化\n",
    "        self.bias = nn.Parameter(torch.zeros(out_features))  #Parameter是nn.Module的子类，可以被自动求导，可以被优化器优化\n",
    "        \n",
    "    def forward(self, x):\n",
    "        return torch.mm(x, self.weights) + self.bias # 全连接层的前向计算,mm是矩阵乘法"
   ],
   "outputs": [],
   "execution_count": 9
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.769920Z",
     "start_time": "2025-02-26T12:19:41.766374Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            CustomizedLinear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            CustomizedLinear(30, 1)\n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 10
  },
  {
   "cell_type": "code",
   "source": [
    "model = NeuralNetwork()\n",
    "for name, param in model.named_parameters():\n",
    "    print(name, param.shape)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.780327Z",
     "start_time": "2025-02-26T12:19:41.770423Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "linear_relu_stack.0.weights torch.Size([8, 30])\n",
      "linear_relu_stack.0.bias torch.Size([30])\n",
      "linear_relu_stack.2.weights torch.Size([30, 1])\n",
      "linear_relu_stack.2.bias torch.Size([1])\n"
     ]
    }
   ],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "source": [
    "for m in model.modules():\n",
    "    print(m)\n",
    "    print('-'*50)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.799037Z",
     "start_time": "2025-02-26T12:19:41.781329Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NeuralNetwork(\n",
      "  (linear_relu_stack): Sequential(\n",
      "    (0): CustomizedLinear()\n",
      "    (1): ReLU()\n",
      "    (2): CustomizedLinear()\n",
      "  )\n",
      ")\n",
      "--------------------------------------------------\n",
      "Sequential(\n",
      "  (0): CustomizedLinear()\n",
      "  (1): ReLU()\n",
      "  (2): CustomizedLinear()\n",
      ")\n",
      "--------------------------------------------------\n",
      "CustomizedLinear()\n",
      "--------------------------------------------------\n",
      "ReLU()\n",
      "--------------------------------------------------\n",
      "CustomizedLinear()\n",
      "--------------------------------------------------\n"
     ]
    }
   ],
   "execution_count": 12
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.803769Z",
     "start_time": "2025-02-26T12:19:41.800039Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 13
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:41.808082Z",
     "start_time": "2025-02-26T12:19:41.804771Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 14
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:57.675757Z",
     "start_time": "2025-02-26T12:19:41.809114Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/4600 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "68f22c1596914a6289a2bb9ef896ecdd"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 15
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:57.752041Z",
     "start_time": "2025-02-26T12:19:57.675757Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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1vDhw62PP/643Ic2ZC1ZssT68Y9/bN1zzz06ashavHhxt8d/8YtfWMOGDbPuvfde69VXX7VOOukka8qUKVZnZ2d+n2OPPdaaMWOG9eyzz1pPPPGEtffee1tnnnlm/vHm5mZr9OjR1vz586033njDuuOOO6yqqirr5ptvLulndbt58+ZZt912m/mOXnnlFev444+3Jk6caLW1teX3Oe+886wJEyZYy5cvt1544QXr05/+tPWZz3wm/3gqlbKmT59uzZ4923r55ZdN+TY2NlqXXXZZfp9Vq1ZZsVjMuvjii6233nrLuu6666xQKGQ99NBDJf/MbvW3v/3NeuCBB6x33nnHWrlypfWjH/3IikQipmwU5VB6zz//vDV58mTr4IMPti666KL8/ZRF+XgiWHzqU5+yFixYkL+dTqetvfbay1q0aFFZj8sregaLTCZjjRkzxvr1r3+dv2/79u1WNBo14UDpL6E+b8WKFfl9HnzwQSsQCFgffvihuX3DDTdYI0aMsOLxeH6fSy+91Np3331L9MmGpk2bNpnv9rHHHst/93py+5//+Z/8Pv/4xz/MPs8884y5rX80g8GgtXHjxvw+N954o1VXV5f//n/wgx9YBx54YLf3+upXv2qCDXZNf4ZvueUWyqEMWltbrX322cdatmyZ9fnPfz4fLCiL8hryTSGJREJefPFFUxVfuB6J3n7mmWfKemxetXr1arOybeF3rvPHaxOU/Z3rpTZ/zJo1K7+P7q9l89xzz+X3Oeqoo6SioiK/z7x580w1/7Zt20r6mYaS5uZmc1lfX28u9edfl4EuLI/99ttPJk6c2K08DjrooG6rEOt3rYsxvfnmm/l9Cl/D3offo96l02m58847pb293TSJUA6lp00d2pTR8/uiLMqr5IuQOa2pqcn8gvdctl1vv/3222U7Li/TUKF6+87tx/RS2ywLhcNhczIs3GfKlCk7vYb92IgRI4r6OYYiXR1Y25GPPPJImT59ev670nCmQW535dFbedmP7W4f/UPb2dlp+tJA5PXXXzdBQtvwte1+8eLFcsABB8grr7xCOZSQhrqXXnpJVqxYsdNj/E6U15APFoDf/kN744035Mknnyz3ofjWvvvua0KE1hzdfffdctZZZ8ljjz1W7sPyFV3u/KKLLpJly5aZjt9wlyHfFNLY2CihUGin3r56e8yYMWU7Li+z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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 16
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T12:19:57.785917Z",
     "start_time": "2025-02-26T12:19:57.753039Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3781\n"
     ]
    }
   ],
   "execution_count": 17
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
